package com.haima.sage.bigdata.api.frame.process.impl;

import com.haima.sage.bigdata.api.common.Constants;
import com.haima.sage.bigdata.api.frame.condition.impl.ConditionFactory;
import com.haima.sage.bigdata.api.frame.condition.impl.ConditionService;
import com.haima.sage.bigdata.api.frame.datatype.impl.DateScriptDataTypeService;
import com.haima.sage.bigdata.api.frame.datatype.impl.DateStringDataTypeService;
import com.haima.sage.bigdata.api.frame.interpreter.ValueService;
import com.haima.sage.bigdata.api.frame.interpreter.ValueServiceFactory;
import com.haima.sage.bigdata.api.entity.api.Api;
import com.haima.sage.bigdata.api.frame.process.ApiProcess;
import com.haima.sage.bigdata.api.entity.api.data.ApiData;
import com.haima.sage.bigdata.api.frame.filter.Filter;
import com.haima.sage.bigdata.api.frame.filter.FilterService;
import com.haima.sage.bigdata.api.entity.api.data.ConstConfig;
import com.haima.sage.bigdata.api.entity.api.data.ConstType;
import com.haima.sage.bigdata.api.entity.api.data.cases.Case;
import com.haima.sage.bigdata.api.entity.api.data.ApiParam;
import com.haima.sage.bigdata.api.entity.api.ResultType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;

public abstract class BaseApiProcess<CLIENT extends AutoCloseable, RESULT> extends ApiProcess {
    private static final Logger LOG = LogManager.getLogger(BaseApiProcess.class);
    protected Map<String, Object> consts;

    protected ApiData data;
    protected Case item;
    protected ConditionService<?> conditionService;
    protected ValueService interpreter;
    protected List<FilterService<?>> filterServices;

    protected BaseApiProcess(Api api, ApiData data, Case item) throws Exception {
        super(api);
        if (item.getConfig() == null) {
            throw new IllegalArgumentException("api.case.config empty, id:" + api.getId());
        }
        this.data = data;
        this.item = item;
        this.conditionService = ConditionFactory.getInstance(item.getConditions());
        this.consts = initConsts(data);
        this.interpreter = ValueServiceFactory.getInstance(item.getConfig().getData());
        this.filterServices = initFilterServices();
        // 如果const和param同名，param传的值为空时作为默认值。
    }

    public Case getCase() {
        return item;
    }

    protected abstract CLIENT getClient() throws Exception;

    protected List<FilterService<?>> initFilterServices() {
        if (item.getConfig().getFilters() == null) {
            return null;
        }
        List<FilterService<?>> services = new ArrayList<>(item.getConfig().getFilters().size());
        for (Filter filter : item.getConfig().getFilters()) {
            FilterService<?> service1 = filter.service();
            services.add(service1);
        }
        return services;
    }

    protected Map<String, Object> initParams(ApiData data, Map<String, ?> params) throws Exception {
        // params
        Map<String, Object> map = new HashMap<>();
        if (ResultType.Page == api.getResult()) {
            Object from = params.get(Constants.FROM);
            if (from != null) {
                map.put(Constants.FROM, from.toString());
            }
            Object size = params.get(Constants.SIZE);
            if (size != null) {
                map.put(Constants.SIZE, size.toString());
            }
        }
        if (data.getParams() != null && params != null) {
            for (ApiParam param : data.getParams()) {
                Object value = params.get(param.getName());
                if (value != null) {
                    map.put(param.getName(), value.toString());
                }
            }
        }
        return map;
    }

    private Map<String, Object> initConsts(ApiData data) throws Exception {
        Map<String, Object> map = new HashMap<>();

        if (data.getConsts() != null) {
            DateStringDataTypeService service1 = new DateStringDataTypeService(Constants.DATE_PATTERN, Constants.DATE_PATTERN);
            DateScriptDataTypeService service2 = new DateScriptDataTypeService();
            for (ConstConfig dc : data.getConsts()) {
                String dist = null;
                ConstType type = dc.getType();
                if (type == null) {
                    type = ConstType.String;
                }
                switch (type) {
                    case FormatDate:
                        dist = service1.trans(dc.getValue());
                        break;
                    case ScriptDate:
                        Date date = service2.trans(dc.getValue());
                        dist = service1.trans(date);
                        break;

                    default:
                        dist = dc.getValue();
                }
                map.put(dc.getName(), dist);
            }
        }
        return map;
    }


    @Override
    public boolean condition(Map<String, ?> params) {
        try {
            return conditionService.deal(params);
        } catch (Exception e) {
            LOG.warn("condition error", e);
        }
        return false;
    }

    protected Map<String, Object> doFilter(Map<String, Object> map) {
        if (this.filterServices != null) {
            for (FilterService<?> filterService : filterServices) {
                map = filterService.filter(map);
                if (map == null) {
                    break;
                }
            }
        }
        return map;
    }

    @Override
    public Object execute(Map<String, ?> params) throws Exception {
        try (CLIENT client = getClient()) {
            if (client == null) {
                throw new Exception("can't get client");
            }
            Map<String, String> map = porcessParamsAndConsts(params);
            String sql = interpreter.value(map);
            LOG.debug("sql: {}", sql);
            RESULT rs = getResult(sql, client);
            if (api.getResult() == null) {
                return getListResult(sql, rs);
            } else {
                switch (api.getResult()) {
                    case Column:
                        return getColumnResult(sql, rs);
                    case Page:
                        return getPagerResult(sql, rs);
                    case Row:
                        return getRowResult(sql, rs);
                    case List:
                    default:
                        return getListResult(sql, rs);
                }
            }
        }
    }

    protected abstract RESULT getResult(String sql, CLIENT client) throws Exception;

    protected abstract Object getRowResult(String sql, RESULT rs) throws Exception;

    protected abstract Object getPagerResult(String sql, RESULT rs) throws Exception;

    protected abstract Object getColumnResult(String sql, RESULT rs) throws Exception;

    protected abstract Object getListResult(String sql, RESULT rs) throws Exception;

    protected Map<String, String> porcessParamsAndConsts(Map<String, ?> params) throws Exception {
        Map<String, String> map = new HashMap<>();
        Map<String, Object> paramsMap = initParams(data, params);
        if (paramsMap != null) {
            for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
                String value = entry.getValue().toString();
                value = senseInject(value);
                map.put(entry.getKey(), value);
            }
            for (Map.Entry<String, Object> entry : this.consts.entrySet()) {
                String value = map.get(entry.getKey());
                if (value == null) {
                    value = entry.getValue().toString();
                    value = senseInject(value);
                    map.put(entry.getKey(), value);
                }
            }
        }
        return map;
    }

    private String senseInject(String value) {
        String dist = value;
        dist = dist.replace("\\", "\\\\");
        dist = dist.replace("-", "\\-");
        dist = dist.replace("'", "\\'");
        dist = dist.replace(";", "\\;");
        return dist;
    }
}
